#ifndef CH_TREECTRL_H
#define CH_TREECTRL_H

#pragma once

#include "CHGlobal.h"

#define CH_TREE_BASE	100
#define CH_TREE_SELECT	CH_TREE_BASE + 1
#define CH_TREE_DELETE	CH_TREE_BASE + 2
#define CH_TREE_FIND	CH_TREE_BASE + 3
#define CH_TREE_CLOSE	CH_TREE_BASE + 4

#define chtNewNode		0
#define chtReadChild	1
#define chtReadGrand	2

typedef struct CH_NODEINFO
{
	DWORD		State;
	DWORD		Turn;
	DWORD		Type;
	CTreeCtrl*	pCtrl;
	HTREEITEM	Item;
}
NODEINFO, *PNODEINFO;


class CHTreeCtrl : public CTreeCtrl
{

public:	CHTreeCtrl()
		{
			m_bDragging = false; 
			m_hParent = NULL; 
		}

public:	virtual ~CHTreeCtrl() 
		{
		}

protected: virtual void InitDataSize() = 0;

public: void Init(HWND hParent, DWORD nID)
		{
			ASSERT(IsWindow(hParent));

			m_hParent = hParent;
			m_nID = nID;

			InitDataSize();
			m_nRootData = (DWORD) (PNODEINFO) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, m_nDataSize + sizeof(NODEINFO));
			PNODEINFO pNode = (PNODEINFO) m_nRootData;
			pNode->State = chtNewNode;
			pNode->Item = TVI_ROOT;
			pNode->pCtrl = (CTreeCtrl*) this;
			pNode->Turn = 0;
			pNode->Type = 0;
		}

public: DWORD GetUserData(HTREEITEM hItem)
		{
			if(hItem == TVI_ROOT || hItem == 0)
				return GetItemDataEx(TVI_ROOT) + sizeof(NODEINFO);
			else
				return GetItemDataEx(hItem) + sizeof(NODEINFO);
		}

protected: DWORD GetItemDataEx(HTREEITEM hItem)
		   {
			   ASSERT(hItem);

			   if(hItem != TVI_ROOT)
				   return GetItemData(hItem);
			   else
				   return m_nRootData;
		   }

public: CString GetNodePath(HTREEITEM hItem)
		{
			if (hItem == 0 || hItem == TVI_ROOT)
				return _T("");

			CString strPath;
			while(hItem != 0 && hItem != TVI_ROOT)
			{
				strPath = _T("/") + GetItemText(hItem) + strPath;
				hItem = GetParentItem(hItem);
			}

			return strPath;
		}

protected: virtual BOOL CanDragTo(HTREEITEM hDragItemS, HTREEITEM hDragItemD) = 0;
protected: virtual void ChangeParent(HTREEITEM hItem, HTREEITEM hParentItem) = 0;
protected: HTREEITEM CopyBranch(HTREEITEM htiBranch, HTREEITEM htiNewParent, HTREEITEM htiAfter)
		   {
			   HTREEITEM  hChild;
			   HTREEITEM  hNewItem = CopyItem(htiBranch, htiNewParent, htiAfter);
			   hChild = GetChildItem(htiBranch);

			   while(hChild != NULL)
			   {
				   CopyBranch(hChild, hNewItem, htiAfter);
				   hChild = GetNextSiblingItem(hChild);
			   }

			   return  hNewItem;
		   }

protected: HTREEITEM CopyItem(HTREEITEM hItem, HTREEITEM htiNewParent, HTREEITEM htiAfter)
		   {
			   TV_INSERTSTRUCT  tvstruct;
			   HTREEITEM        hNewItem;
			   CString          sText;

			   tvstruct.item.hItem = hItem;
			   tvstruct.item.mask  = TVIF_CHILDREN|TVIF_HANDLE|TVIF_IMAGE|TVIF_SELECTEDIMAGE;
			   GetItem(&tvstruct.item);
			   sText = GetItemText(hItem);
			   tvstruct.item.cchTextMax = sText.GetLength();
			   tvstruct.item.pszText    = sText.LockBuffer();

			   tvstruct.hParent         = htiNewParent;
			   tvstruct.hInsertAfter    = htiAfter;
			   tvstruct.item.mask       = TVIF_IMAGE|TVIF_SELECTEDIMAGE|TVIF_TEXT;
			   hNewItem = InsertItem(&tvstruct);

			   sText.ReleaseBuffer();

			   PNODEINFO pNode = (PNODEINFO) GetItemData(hItem);
			   ASSERT(pNode);
			   pNode->Item = hNewItem;
			   SetItemData(hNewItem, (DWORD) pNode);

			   SetItemState(hNewItem, GetItemState(hItem, TVIS_STATEIMAGEMASK), TVIS_STATEIMAGEMASK);

			   return hNewItem;
		   }


protected: virtual void ReadChildNode(HTREEITEM hItem) = 0;

protected: void ReadGrandNode(HTREEITEM hItem)
		   {
			   ASSERT(hItem);

			   PNODEINFO pNode = (PNODEINFO) GetItemDataEx(hItem);
			   pNode->State = chtReadGrand;

			   HTREEITEM hChild = GetChildItem(hItem);
			   while(hChild != NULL)
			   {
				   ReadChildNode(hChild);
				   hChild = GetNextSiblingItem(hChild);
			   }
		   }

public: void ReadChildGrandNode(HTREEITEM hItem)
		{
			ASSERT(hItem);

			PNODEINFO pNode = (PNODEINFO) GetItemDataEx(hItem);
			switch(pNode->State)
			{
			case chtNewNode:	ReadChildNode(hItem);
			case chtReadChild:	ReadGrandNode(hItem);
			}
			pNode->State = chtReadGrand;
		}

public: void ReadAllChildNode(HTREEITEM hItem)
		{
			ASSERT(hItem);
			ReadChildNode(hItem);
			HTREEITEM hChild = GetChildItem(hItem);
			while(hChild != NULL)
			{
				ReadAllChildNode(hChild);
				hChild = GetNextSiblingItem(hChild);
			}
		}

protected: virtual void InsertNodeEx(PNODEINFO pNode) = 0;

public: HTREEITEM InsertNode(LPCTSTR lpszItem, DWORD nType, BOOL bInsertEx = FALSE, HTREEITEM hParent = TVI_ROOT, HTREEITEM hInsertAfter = TVI_LAST)
		{
			ASSERT(hParent);

			HTREEITEM hItem = InsertItem(lpszItem, nType, nType, hParent, hInsertAfter);
			ASSERT(hItem);

			PNODEINFO pNode = (PNODEINFO) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, m_nDataSize + sizeof(NODEINFO));
			pNode->State = (bInsertEx ? chtReadGrand : chtNewNode);
			pNode->Item = hItem;
			pNode->Turn = 0;
			pNode->pCtrl = (CTreeCtrl*) this;
			pNode->Type = nType;
			SetItemData(hItem, (DWORD)pNode);

			if(bInsertEx) InsertNodeEx(pNode);

			return hItem;
		}

public: virtual void SelectNodeEx(HTREEITEM hItem) = 0;

protected: virtual void DeleteNodeEx(PNODEINFO pNode) = 0;

protected: void DeleteNodeImpl(HTREEITEM hItem)
		   {
			   ASSERT(hItem);

			   PNODEINFO pNode = (PNODEINFO) GetItemDataEx(hItem);
			   DeleteNodeEx(pNode);
			   FreeNodeOnly(hItem);

			   HTREEITEM hChild = GetChildItem(hItem);
			   while(hChild != NULL)
			   {
				   DeleteNodeImpl(hChild);
				   hChild = GetNextSiblingItem(hChild);
			   }
		   }

public: BOOL DeleteNode(HTREEITEM hItem)
		{
			ASSERT(hItem);

			ReadAllChildNode(hItem);
			DeleteNodeImpl(hItem);
			return DeleteItem(hItem);
		}

protected: void FreeNodeOnly(HTREEITEM hItem)
		   {
			   ASSERT(hItem);

			   DWORD nData = GetItemDataEx(hItem);
			   HeapFree(GetProcessHeap(), 0, (PVOID)nData);
		   }

public: void FreeNode(HTREEITEM hItem)
		{
			ASSERT(hItem);
			FreeNodeOnly(hItem);

			HTREEITEM hChild = GetChildItem(hItem);
			while(hChild != NULL)
			{
				FreeNode(hChild);
				hChild = GetNextSiblingItem(hChild);
			}
		}

protected: void SortChildrenX(HTREEITEM hItem)
		   {
			   TVSORTCB tvs;
			   tvs.hParent = hItem;
			   tvs.lpfnCompare = TreeViewCompareProc;
			   tvs.lParam = 0;
			   SortChildrenCB(&tvs);
		   }

protected: static int CALLBACK TreeViewCompareProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
		   {
			   PNODEINFO pNode1 = (PNODEINFO) lParam1;
			   PNODEINFO pNode2 = (PNODEINFO) lParam2;

			   if (pNode1->Turn > pNode2->Turn)
				   return 1;
			   else
				   return -1;
		   }

protected: virtual void ChangeTurnEx(PNODEINFO pNode) = 0;

public: void ChangeTurn(HTREEITEM hItem, BOOL bUp)
		{
			ASSERT(hItem != TVI_ROOT && hItem != 0);

			HTREEITEM hNext = bUp ? GetPrevSiblingItem(hItem) : GetNextSiblingItem(hItem);
			if(hNext == NULL)
				return;

			PNODEINFO pNode = (PNODEINFO) GetItemData(hItem);
			PNODEINFO pNext = (PNODEINFO) GetItemData(hNext);

			int nTurn = pNode->Turn;
			pNode->Turn = pNext->Turn;
			pNext->Turn = nTurn;

			ChangeTurnEx(pNode);
			ChangeTurnEx(pNext);

			SortChildrenX(GetParentItem(hItem));
		}

protected:
	UINT          m_TimerTicks;
	UINT          m_nScrollTimerID;
	CPoint        m_HoverPoint;
	UINT          m_nHoverTimerID;
	DWORD         m_dwDragStart;
	BOOL          m_bDragging;
	CImageList*   m_pDragImage;
	HTREEITEM     m_hItemDragS;
	HTREEITEM     m_hItemDragD;

	HWND		  m_hParent;
	DWORD		  m_nID;
	DWORD		  m_nDataSize;
	DWORD		  m_nRootData;

protected:
	afx_msg void OnBegindrag(NMHDR* pNMHDR, LRESULT* pResult);
	afx_msg void OnMouseMove(UINT nFlags, CPoint point);
	afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
	afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
	afx_msg void OnTimer(UINT nIDEvent);
	afx_msg void OnItemexpanded(NMHDR* pNMHDR, LRESULT* pResult);
	afx_msg void OnDestroy();

	DECLARE_MESSAGE_MAP()
};

#endif //CH_TREECTRL_H
